/* ****************************************************************** **
**    OpenSees - Open System for Earthquake Engineering Simulation    **
**          Pacific Earthquake Engineering Research Center            **
**                                                                    **
**                                                                    **
** (C) Copyright 1999, The Regents of the University of California    **
** All Rights Reserved.                                               **
**                                                                    **
** Commercial use of this program without express permission of the   **
** University of California, Berkeley, is strictly prohibited.  See   **
** file 'COPYRIGHT'  in main directory for information on usage and   **
** redistribution,  and for a DISCLAIMER OF ALL WARRANTIES.           **
**                                                                    **
** Developed by:                                                      **
**   Frank McKenna (fmckenna@ce.berkeley.edu)                         **
**   Gregory L. Fenves (fenves@ce.berkeley.edu)                       **
**   Filip C. Filippou (filippou@ce.berkeley.edu)                     **
**                                                                    **
** ****************************************************************** */

// Written: User-defined implementation for UDP hybrid simulation commands
// Purpose: This file contains the implementation for UDP related Tcl commands.

#include <tcl.h>
#include <TclModelBuilder.h>
#include <EquiSolnAlgo.h>
#include <ConvergenceTest.h>
#include <Domain.h>
#include <domain/pattern/UDP_LoadPattern.h>
#include <recorder/UDP_Recorder.h>
#include <OPS_Globals.h>
#include <AnalysisModel.h>

// Include the GPU Newton-Raphson header
#include "../analysis/integrator/GPU_NewtonRaphson.h"

extern Domain *theDomain;

// Helper functions for TclModelBuilder compatibility
ConvergenceTest* getConvergenceTest(TclModelBuilder *theTclBuilder) {
    // 简化实现 - 因为TclModelBuilder没有直接获取ConvergenceTest的方法
    // 这里返回nullptr，在实际使用时可能需要根据TclModelBuilder的具体接口进行修改
    return nullptr;
}

int setAlgorithm(TclModelBuilder *theTclBuilder, EquiSolnAlgo& newAlgo) {
    // 简化实现 - 将算法设置到TclModelBuilder
    // 在实际使用时可能需要根据TclModelBuilder的具体接口进行修改
    return 0;
}

// External function declaration for registration
extern "C" int
TclModelBuilder_addGPU_NewtonRaphson(ClientData clientData, 
                                    Tcl_Interp *interp, 
                                    int argc, 
                                    TCL_Char **argv, 
                                    Domain *theDomain,
                                    TclModelBuilder *theTclBuilder)
{
    // Default parameters
    int maxElements = 50000;
    int maxDOFs = 150000;
    int maxNodes = 50000;
    bool profiling = false;
    bool cpuOnly = false;
    ConvergenceTest* theTest = nullptr;
    
    // Parse command line arguments
    int currentArg = 1;
    while (currentArg < argc) {
        if (strcmp(argv[currentArg], "-maxElements") == 0) {
            currentArg++;
            if (currentArg < argc) {
                if (Tcl_GetInt(interp, argv[currentArg], &maxElements) != TCL_OK) {
                    opserr << "WARNING: invalid maxElements in GPU_NewtonRaphson command\n";
                    return TCL_ERROR;
                }
            } else {
                opserr << "WARNING: no maxElements value provided\n";
                return TCL_ERROR;
            }
        }
        else if (strcmp(argv[currentArg], "-maxDOFs") == 0) {
            currentArg++;
            if (currentArg < argc) {
                if (Tcl_GetInt(interp, argv[currentArg], &maxDOFs) != TCL_OK) {
                    opserr << "WARNING: invalid maxDOFs in GPU_NewtonRaphson command\n";
                    return TCL_ERROR;
                }
            } else {
                opserr << "WARNING: no maxDOFs value provided\n";
                return TCL_ERROR;
            }
        }
        else if (strcmp(argv[currentArg], "-maxNodes") == 0) {
            currentArg++;
            if (currentArg < argc) {
                if (Tcl_GetInt(interp, argv[currentArg], &maxNodes) != TCL_OK) {
                    opserr << "WARNING: invalid maxNodes in GPU_NewtonRaphson command\n";
                    return TCL_ERROR;
                }
            } else {
                opserr << "WARNING: no maxNodes value provided\n";
                return TCL_ERROR;
            }
        }
        else if (strcmp(argv[currentArg], "-profiling") == 0) {
            profiling = true;
        }
        else if (strcmp(argv[currentArg], "-cpu-only") == 0) {
            cpuOnly = true;
        }
        else if (strcmp(argv[currentArg], "-test") == 0) {
            currentArg++;
            if (currentArg < argc) {
                // Get the convergence test from the model builder
                theTest = getConvergenceTest(theTclBuilder);
                if (theTest == nullptr) {
                    opserr << "WARNING: no convergence test defined\n";
                    return TCL_ERROR;
                }
            }
        }
        else {
            opserr << "WARNING: unknown option " << argv[currentArg] << " in GPU_NewtonRaphson command\n";
        }
        currentArg++;
    }
    
    // Validate parameters
    if (maxElements <= 0 || maxDOFs <= 0 || maxNodes <= 0) {
        opserr << "ERROR: invalid problem size parameters in GPU_NewtonRaphson\n";
        return TCL_ERROR;
    }
    
    // Create GPU Newton-Raphson algorithm
    EquiSolnAlgo *theNewAlgo = nullptr;
    
    try {
        if (theTest != nullptr) {
            theNewAlgo = new GPU_NewtonRaphson(*theTest, maxElements, maxDOFs, maxNodes);
        } else {
            theNewAlgo = new GPU_NewtonRaphson(maxElements, maxDOFs, maxNodes);
        }
        
        GPU_NewtonRaphson* gpuAlgo = static_cast<GPU_NewtonRaphson*>(theNewAlgo);
        
        // Configure the algorithm
        if (profiling) {
            gpuAlgo->enableProfiling(true);
            opserr << "GPU profiling enabled\n";
        }
        
        if (cpuOnly) {
            gpuAlgo->enableGPU(false);
            opserr << "GPU disabled, using CPU fallback\n";
        }
        
        // Set the algorithm in the model builder
        if (setAlgorithm(theTclBuilder, *theNewAlgo) != 0) {
            opserr << "ERROR: failed to set GPU_NewtonRaphson algorithm\n";
            delete theNewAlgo;
            return TCL_ERROR;
        }
        
        // Print configuration info
        opserr << "GPU_NewtonRaphson algorithm created\n";
        opserr << "  Max elements: " << maxElements << "\n";
        opserr << "  Max DOFs: " << maxDOFs << "\n";
        opserr << "  Max nodes: " << maxNodes << "\n";
        opserr << "  GPU enabled: " << (cpuOnly ? "No" : "Yes") << "\n";
        opserr << "  Profiling: " << (profiling ? "Yes" : "No") << "\n";
        
        return TCL_OK;
        
    } catch (const std::exception& e) {
        opserr << "ERROR: Failed to create GPU_NewtonRaphson: " << e.what() << endln;
        if (theNewAlgo != nullptr) {
            delete theNewAlgo;
        }
        return TCL_ERROR;
    } catch (...) {
        opserr << "ERROR: Unknown exception in GPU_NewtonRaphson creation\n";
        if (theNewAlgo != nullptr) {
            delete theNewAlgo;
        }
        return TCL_ERROR;
    }
}

// Function to print usage information
extern "C" void
printGPU_NewtonRaphsonUsage()
{
    opserr << "Usage: algorithm GPU_NewtonRaphson [options]\n";
    opserr << "Options:\n";
    opserr << "  -maxElements <int>  : Maximum number of elements (default: 50000)\n";
    opserr << "  -maxDOFs <int>      : Maximum number of DOFs (default: 150000)\n";
    opserr << "  -maxNodes <int>     : Maximum number of nodes (default: 50000)\n";
    opserr << "  -profiling          : Enable performance profiling\n";
    opserr << "  -cpu-only           : Disable GPU acceleration\n";
    opserr << "  -test               : Use predefined convergence test\n";
    opserr << "\nExample:\n";
    opserr << "  algorithm GPU_NewtonRaphson -maxElements 25000 -maxDOFs 75000 -profiling\n";
}

// Command: pattern UDP $patternTag $nodeTag $dof $port <-nonBlocking>
int TclCommand_addUDP_LoadPattern(ClientData clientData, Tcl_Interp *interp, int argc, TCL_Char **argv)
{
    if (argc < 6) {
        opserr << "WARNING insufficient arguments\n";
        opserr << "Want: pattern UDP patternTag nodeTag dof port <-nonBlocking>" << endln;
        return TCL_ERROR;
    }
    
    int patternTag, nodeTag, dof, port;
    bool waitForData = true;
    
    if (Tcl_GetInt(interp, argv[2], &patternTag) != TCL_OK) {
        opserr << "WARNING invalid patternTag\n";
        opserr << "pattern UDP patternTag nodeTag dof port <-nonBlocking>" << endln;
        return TCL_ERROR;
    }
    
    if (Tcl_GetInt(interp, argv[3], &nodeTag) != TCL_OK) {
        opserr << "WARNING invalid nodeTag\n";
        opserr << "pattern UDP patternTag nodeTag dof port <-nonBlocking>" << endln;
        return TCL_ERROR;
    }
    
    if (Tcl_GetInt(interp, argv[4], &dof) != TCL_OK) {
        opserr << "WARNING invalid dof\n";
        opserr << "pattern UDP patternTag nodeTag dof port <-nonBlocking>" << endln;
        return TCL_ERROR;
    }
    
    if (Tcl_GetInt(interp, argv[5], &port) != TCL_OK) {
        opserr << "WARNING invalid port\n";
        opserr << "pattern UDP patternTag nodeTag dof port <-nonBlocking>" << endln;
        return TCL_ERROR;
    }
    
    // Check for -nonBlocking option
    if (argc > 6) {
        if (strcmp(argv[6], "-nonBlocking") == 0) {
            waitForData = false;
        }
    }
    
    UDP_LoadPattern *thePattern = new UDP_LoadPattern(patternTag, nodeTag, dof, port, waitForData);
    
    if (thePattern == 0) {
        opserr << "WARNING could not create UDP_LoadPattern\n";
        return TCL_ERROR;
    }
    
    if (theDomain->addLoadPattern(thePattern) == false) {
        opserr << "WARNING could not add UDP_LoadPattern to domain\n";
        delete thePattern;
        return TCL_ERROR;
    }
    
    return TCL_OK;
}

// Command: recorder UDP $recorderTag $nodeTag $dof $serverIP $port
int TclCommand_addUDP_Recorder(ClientData clientData, Tcl_Interp *interp, int argc, TCL_Char **argv)
{
    if (argc < 7) {
        opserr << "WARNING insufficient arguments\n";
        opserr << "Want: recorder UDP recorderTag nodeTag dof serverIP port" << endln;
        return TCL_ERROR;
    }
    
    int recorderTag, nodeTag, dof, port;
    const char *serverIP;
    
    if (Tcl_GetInt(interp, argv[2], &recorderTag) != TCL_OK) {
        opserr << "WARNING invalid recorderTag\n";
        opserr << "recorder UDP recorderTag nodeTag dof serverIP port" << endln;
        return TCL_ERROR;
    }
    
    if (Tcl_GetInt(interp, argv[3], &nodeTag) != TCL_OK) {
        opserr << "WARNING invalid nodeTag\n";
        opserr << "recorder UDP recorderTag nodeTag dof serverIP port" << endln;
        return TCL_ERROR;
    }
    
    if (Tcl_GetInt(interp, argv[4], &dof) != TCL_OK) {
        opserr << "WARNING invalid dof\n";
        opserr << "recorder UDP recorderTag nodeTag dof serverIP port" << endln;
        return TCL_ERROR;
    }
    
    serverIP = argv[5];
    
    if (Tcl_GetInt(interp, argv[6], &port) != TCL_OK) {
        opserr << "WARNING invalid port\n";
        opserr << "recorder UDP recorderTag nodeTag dof serverIP port" << endln;
        return TCL_ERROR;
    }
    
    UDP_Recorder *theRecorder = new UDP_Recorder(nodeTag, dof, serverIP, port);
    
    if (theRecorder == 0) {
        opserr << "WARNING could not create UDP_Recorder\n";
        return TCL_ERROR;
    }
    
    if (theDomain->addRecorder(*theRecorder) < 0) {
        opserr << "WARNING could not add UDP_Recorder to domain\n";
        delete theRecorder;
        return TCL_ERROR;
    }
    
    return TCL_OK;
}